package ru.chaykin.load.parse;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import ru.chaykin.exceptions.InvalidFileFormatException;
import ru.chaykin.graphdrawers.Pen;
import ru.chaykin.load.TextureData;
import ru.chaykin.primitives.Circle;
import ru.chaykin.primitives.Point;
import ru.chaykin.primitives.Primitives;
import ru.chaykin.quadtree.Quad;
import ru.chaykin.quadtree.QuadTree;
import ru.chaykin.quadtree.QuadTreeBuilder;
import ru.chaykin.rendering.CullingThread;
import ru.chaykin.rendering.OpenGLSceneRenderer;
import ru.chaykin.util.FileNameFormatHelper;
import ru.chaykin.viewobject.ViewObject;
import ru.chaykin.viewobject.geometry.Geometry;

public final class MIFParser implements IRegExpsPatternsConsts {
	
	private static List<String> columnNames;
	private static Pattern B_PEN_PATT;
	private static Pattern B_FONT_PATT;
	
	private static final int IN_BUFF_SIZE = Float.SIZE * 32 * 1024;	//Буффер размером 1 Мб
	
	private static String sep = ",";	//Разделитель в файле MIF
	
	private BufferedReader in;
	
	private static int totalObjects = 0; //Общее количество найденных объектов. Нужно для отображения инфы из MID-файла
	
	private static QuadTreeBuilder quadBuilder;
	private static QuadTree quadTree;
	
	public static List<TextureData> textureDataBuff = new ArrayList<TextureData>(4096);
	
	public MIFParser(String fName) throws InvalidFileFormatException, IOException {
		openFile(fName);
		totalObjects = 0;
		B_PEN_PATT = Pattern.compile("\\s*Pen\\s+\\((\\d+)" + sep + "\\s*(\\d+)" + sep + "\\s*(\\d+)\\s*\\)\\s*", Pattern.CASE_INSENSITIVE);
		B_FONT_PATT = Pattern.compile("\\s*Font\\s+\\(\"(.*)\"\\s*" + sep + "\\s*(\\d+)\\s*" + sep + "\\s*(\\d+)\\s*" + sep + "\\s*(\\d+).*" + 
																	  "\\)\\s*", Pattern.CASE_INSENSITIVE);
	}
	
	/**
	 * Читает из файла и создает очередной объект
	 * @param str - строка, с которой начинается объект
	 */
	public void instanceNextObject(String str) throws IOException {
		int index = str.indexOf(" ");
		if (index != -1) {
			String objType = str.trim().substring(0, index);
		}
		
		if (!findLine(B_LINE_PATT.matcher(str))) 
			if (!findPline(B_PLINE_PATT.matcher(str)))
				if(!findRegion(B_REGION_PATT.matcher(str)))
					if(!findText(B_TEXT_PATT.matcher(str)))
						if(!findOther(B_POINT_PATT.matcher(str)))
							if(!findOther(B_ARC_PATT.matcher(str)))
								if(!findOther(B_RECT_PATT.matcher(str)))
									if(!findOther(B_ROUNDRECT_PATT.matcher(str)))
										findOther(B_ELLIPSE_PATT.matcher(str));
	}
	
	private final Pen findPen() throws IOException {
		Matcher matcher;
		Pen pen = new Pen();
		try {
			matcher = B_PEN_PATT.matcher(in.readLine());
		} catch (IOException e) {
			in.close();
			throw new IOException();
		}
		if (matcher.matches()) 
			pen = new Pen(Float.parseFloat(matcher.group(1)), Integer.parseInt(matcher.group(2)), Integer.parseInt(matcher.group(3)));
		return pen;
	}
	
	private boolean findLine(Matcher matcher) throws IOException {
		if (matcher.matches()) {
			totalObjects++;
			float line_x0 = Float.parseFloat(matcher.group(1)); 
			float line_y0 = Float.parseFloat(matcher.group(3)); 
			float line_x1 = Float.parseFloat(matcher.group(5)); 
			float line_y1 = Float.parseFloat(matcher.group(7));
			try {
				Pen pen = findPen();
				Circle circle = Primitives.getCircle(line_x0, line_y0, line_x1, line_y1);
				FloatBuffer pnts = Primitives.getFloatLineBuffer(line_x0, line_y0, line_x1, line_y1);
				quadBuilder.addObject(new Geometry(circle, pen, totalObjects, pnts));
			} catch (IOException e) {
				in.close();
				throw new IOException();
			}
			return true;
		}
		return false;
	}

	private boolean findPline(Matcher matcher) throws IOException {
		if (matcher.matches()) {
			totalObjects++;
			int pLineCount = Integer.parseInt(matcher.group(1));
			List<Point> pLine = new ArrayList<Point>();
			try {
				for (int i = 0; i < pLineCount; i++) {
					matcher = B_FLOAT_POINT_PATT.matcher(in.readLine());
					if (matcher.matches()) {
						pLine.add(new Point(Float.parseFloat(matcher.group(1)), Float.parseFloat(matcher.group(3))));
					}
					else {
						pLine.clear();
						break;
					}
				}
				if (pLine.size() > 0) {
					Circle circle = Primitives.getCircle(pLine);
					Pen pen = findPen();
					FloatBuffer pnts = Primitives.getFloatBuffer(pLine);
					quadBuilder.addObject(new Geometry(circle, pen, totalObjects, pnts));
					return true;
				}
			} catch (IOException e) {
				in.close();
				throw new IOException();
			}
			return true; //В БД не добавили, но сам B_PLINE_PATT сработал
		}
		return false;
	}
	
	private boolean findRegion(Matcher matcher) throws IOException {
		if (matcher.matches()) {
			totalObjects++;
			int regionCount = Integer.parseInt(matcher.group(1));
			try {
				for (int i = 0; i < regionCount; i++) {	//Перебираем несколько полигонов
					List<Point> region = new ArrayList<Point>();
					matcher = B_PNTS_IN_REGION_PATT.matcher(in.readLine());
					if (matcher.matches()) {
						int pntsCount = Integer.parseInt(matcher.group(1));
						for (int j = 0; j < pntsCount; j++) { //Перебираем все точки текущего полигона
							matcher = B_FLOAT_POINT_PATT.matcher(in.readLine());
							if (matcher.matches()) {
								region.add(new Point(Float.parseFloat(matcher.group(1)), Float.parseFloat(matcher.group(3))));
							}
							else {
								region.clear();
								break;
							}
						}
						if (region.size() > 0) {
							Circle circle = Primitives.getCircle(region);
							Pen pen = findPen();
							FloatBuffer pnts = Primitives.getFloatBuffer(region);
							quadBuilder.addObject(new Geometry(circle, pen, totalObjects, pnts));
						}
					}
					else
						break;
				}
			} catch (IOException e) {
				in.close();
				throw new IOException();
			}
			return true;
		}
		return false;
	}
	
	private boolean findText(Matcher matcher) throws IOException {
		if (matcher.matches()) {
			totalObjects++;
			try {
				matcher = B_TEXT_STR_PATT.matcher(in.readLine());
				if (matcher.matches()) {
					String str = matcher.group(1);
					matcher = B_TEXT_POINT_PATT.matcher(in.readLine());
					if (matcher.matches()) {
						float pnts[] = new float[]{Float.parseFloat(matcher.group(1)), Float.parseFloat(matcher.group(3)), 
												   Float.parseFloat(matcher.group(5)), Float.parseFloat(matcher.group(7))};
						matcher = B_FONT_PATT.matcher(in.readLine());
						if (matcher.matches()) {
							int color = Integer.parseInt(matcher.group(3));
							matcher = B_TEXT_ANGLE_PATT.matcher(in.readLine());
							float angle = 0;
							if (matcher.matches())
								angle = Float.parseFloat(matcher.group(1));
							synchronized(textureDataBuff) {
								textureDataBuff.add(new TextureData(pnts, color, angle, totalObjects, str));
							}
						}
					}
					
					return true;
				}
			} catch (IOException e) {
				in.close();
				throw new IOException();
			}
			
		}
		return false;
	}
	
	private boolean findOther(Matcher matcher) throws IOException {
		if (matcher.matches()) { 
			totalObjects++;
			return true;
		}
		return false;
	}
	
	private final void openFile(String fName) throws InvalidFileFormatException, IOException {
    	if (fName !=null /*&& !fName.isEmpty()*/) {
			try {
				Reader reader = new InputStreamReader(new FileInputStream(fName), "CP1251");
				in = new BufferedReader(reader, IN_BUFF_SIZE);
				ViewObject.setMIDFileName(FileNameFormatHelper.getMIDFileName(fName));
			} catch (IOException e) {
				in.close();
				throw new IOException();
			}
			try {
				CheckFileHeader();
			} catch (InvalidFileFormatException e) {
				in.close();
				throw new InvalidFileFormatException();
			} catch (IOException e) {
				in.close();
				throw new IOException();
			}
    	}
    }  
	
	/**
     * Метод проверяет формат файла. Если заголовок не соответствует формату, генерируется исключение <code>InvalidFileFormatException</code>
     * @throws InvalidFileFormatException
     * @throws IOException
     */
    private final void CheckFileHeader() throws InvalidFileFormatException, IOException {
    	String str;
    	boolean verifiedOk = false;
    	Matcher matcher;
		try {
			matcher = H_VERSION_PATT.matcher(in.readLine());
	    	if (matcher.matches()) {
	    		for (int i=0; i<6; i++) {
	    			str = in.readLine();
	    			matcher = H_DELIMETER_PATT.matcher(str);
	    			if (matcher.matches())
	    				sep = matcher.group(1);
	    			else {
		    			matcher = H_COORDSYS_PATT.matcher(str);
		    			if (matcher.matches()) {
		    				float xMin = Float.parseFloat(matcher.group(1)); 
		    				float yMin = Float.parseFloat(matcher.group(3)); 
		    				float xMax = Float.parseFloat(matcher.group(5)); 
		    				float yMax = Float.parseFloat(matcher.group(7));
		    				CullingThread.updateRenderedArea((xMin + xMax) /2f, (yMin + yMax) /2f, Point.distance(xMin, yMin, xMax, yMax) / 2f);
		    				quadBuilder = new QuadTreeBuilder(new Quad(xMin, xMax, yMax, yMin));
		    				quadTree = new QuadTree(QuadTreeBuilder.getRootQuad());
		    				OpenGLSceneRenderer.initRenderedArea();
		    			}
		    			else {
			    			matcher = H_COLUMNS_PATT.matcher(str);
			    			if (matcher.matches()) {
			    				int columns = Integer.parseInt(matcher.group(1));
			    				columnNames = new ArrayList<String>();
			    				for (int j = 0; j < columns; j++) {
			    					matcher = H_COLUMN_NAME_PATT.matcher(in.readLine());
			    					if (matcher.matches())
			    						columnNames.add(matcher.group(1));
			    					else {
			    						in.close();
			    						throw new InvalidFileFormatException();
			    					}
								}
			    				verifiedOk=true;
			    				break;
			    			}
		    			}
	    			}
	    		}
	    		if (!verifiedOk) {
	    			in.close();
	    			throw new InvalidFileFormatException();
	    		}
	    		in.readLine(); //TODO Строка Data, нужно бы тоже проверить
	    	}
	    	else {
	    		in.close();
	    		throw new InvalidFileFormatException();
	    	}
		} catch (IOException e) {
			in.close();
			throw new IOException();
		}
    }

	public BufferedReader getFileReader() {
		return in;
	}
    
	public long getObjectsCount() {
		return totalObjects;
	}
	
	public static List<String> getColumnNames() {
		return columnNames;
	}
	
	public static String getSeparator() {
		return sep;
	}
	
	public static QuadTree getRoot() {
		return quadTree;
	}
}